﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using SoftSource.Common;
using SoftSource.Controls;
using SoftSource.Reactive;
using SoftSource.Timecard.Assets.Resources;
using SoftSource.Timecard.ViewModelInterfaces;
using SoftSource.Timecard.ViewModels;
using SoftSource.Timecard.Web.Data;

namespace SoftSource.Timecard.Controller
{
    public sealed partial class AdministratorController
    {
        #region Parameter-less
        private static IMainViewModelService CreateMainViewModel()
        {
            IMainViewModelService vm = new MainViewModel();

            vm.CreateSummaryView = App.Controller.CreateSummaryView;
            vm.CreateManagementView = App.Controller.CreateManagementView;
            vm.CreateTimeEntryListView = App.Controller.CreateEntryManagementView;
            vm.CreateReportListView = App.Controller.CreateReportView;

            return vm;
        }

        private static IUserListViewModelService CreateUserViewModel()
        {
            IUserListViewModelService vm = new UserListViewModel();

            vm.Load = App.Model.UserSummaries.Load
                .Do(unit => vm.UserSummaries = App.Model.UserSummaries.Entities);

            vm.CreateUserEditor = App.Controller.CreateUserEditor;
            vm.CreateNewUserEditor = App.Controller.CreateNewUserEditor;

            return vm;
        }

        private static ISummaryViewModelService CreateSummaryViewModel()
        {
            ISummaryViewModelService vm = new SummaryViewModel();

            vm.CreatePriorEntriesView = App.Controller.CreateEntriesPriorToProjectBillingDateView;
            vm.CreateUtilizationSummaryView = App.Controller.CreateUtilizationSummaryView;
            vm.CreateEntriesFlaggedForAdminView = App.Controller.CreateEntriesFlaggedForAdminView;

            return vm;
        }

        private static IReportListViewModelService CreateReportViewModel()
        {
            IReportListViewModelService vm = new ReportListViewModel();

            vm.Load = App.Model.Reports.Load
                .Do(unit => vm.ReportCollection = App.Model.Reports.Entities);

            return vm;
        }

        private static IProjectListViewModelService CreateProjectViewModel()
        {
            IProjectListViewModelService vm = new ProjectListViewModel();

            vm.Load = App.Model.ProjectSummaries.Load
                .Do(unit => vm.ProjectSummaries = App.Model.ProjectSummaries.Entities);

            vm.CreateProjectEditor = App.Controller.CreateProjectEditor;
            vm.CreateCloneProjectEditor = App.Controller.CreateCloneProjectEditor;
            vm.CreateNewProjectEditor = App.Controller.CreateNewProjectEditor;

            return vm;
        }

        private static IInvoicingViewModelService CreateInvoicingViewModel()
        {
            IInvoicingViewModelService vm = new InvoicingViewModel();

            vm.Load = App.Model.InvoicingProjects.Load
                .Do(unit => vm.ProjectSource = App.Model.InvoicingProjects.Entities.Where(p => p.StatusID == ProjectStatus.Active));

            // Save the current invoicing projects, then mark the entries as billed for the project
            vm.MarkProjectAsBilledService = (projectID, endDate) => from save in App.Model.InvoicingProjects.Save
                                                                    from mark in App.Model.MarkEntriesAsBilled(projectID, null, endDate)
                                                                    select mark;

            vm.CreateProjectEditor = App.Controller.CreateProjectEditor;

            return vm;
        }

        private static IManagementViewModelService CreateManagementViewModel()
        {
            IManagementViewModelService vm = new ManagementViewModel();

            vm.ManageCategories = App.Controller.CreateCategoryView;
            vm.ManageProjects = App.Controller.CreateProjectView;
            vm.ManageUsers = App.Controller.CreateUserView;
            vm.ManageInvoices = App.Controller.CreateInvoicingView;
            vm.ManageTimecards = App.Controller.CreateUpdateReminderView;

            return vm;
        }

        private static IAutoGridEditorViewModelService CreateCategoryViewModel()
        {
            return SharedController.CreateAutoGridEditorViewModel(
                   App.Model.Categories,
                   ObjectReflection.GetPropertyName<Category>(c => c.Name));
        }

        private static IAutoGridEditorViewModelService CreateCustomerViewModel()
        {
            return SharedController.CreateAutoGridEditorViewModel(
                App.Model.Customers,
                ObjectReflection.GetPropertyName<Customer>(c => c.CustomerName));
        }

        private static IAutoGridEditorViewModelService CreateMilestoneViewModel()
        {
            return SharedController.CreateAutoGridEditorViewModel(
                App.Model.Milestones,
                ObjectReflection.GetPropertyName<Milestone>(m => m.Description),
                true,
                false,
                args => { });
        }

        private static IUpdateReminderViewModelService CreateUpdateReminderViewModel()
        {
            IUpdateReminderViewModelService vm = new UpdateReminderViewModel();

            var load = App.Model
                .GetLatestEntries()
                .Do(entries => vm.EntrySource = entries.Enumerate())
                .Once();

            vm.Load = load;
            vm.CreateGetUserPasswordView = App.Controller.CreateGetUserPasswordView;
            vm.SendReminders = App.Model.SendReminders;

            return vm;
        }

        private static IEntryManagementViewModelService CreateEntryManagementViewModel()
        {
            IEntryManagementViewModelService vm = new EntryManagementViewModel();

            var load = new List<IObservable<Unit>>();
            load.Add(App.Model.ProjectSummaries.Load);
            load.Add(App.Model.UserSummaries.Load);

            vm.Load = load
                .ForkJoin()
                .Do(units => vm.ProjectSource = App.Model.ProjectSummaries.Entities)
                .Do(units => vm.UserSource = App.Model.UserSummaries.Entities)
                .Once();

            vm.Refresh = (projectID, userID, startDate, endDate) =>
                App.Model
                .GetFilteredEntries(projectID, userID, startDate, endDate)
                .Do(entries => vm.EntrySource = entries.Enumerate())
                .Once();

            vm.SaveApprovals = App.Model.Entries.Save.ToUnit();
            vm.EditEntryAssociations = App.Controller.CreateEntryProjectCategoryAssociationEditorView;

            return vm;
        }

        private static IUtilizationSummaryViewModelService CreateUtilizationSummaryViewModel()
        {
            IUtilizationSummaryViewModelService vm = new UtilizationSummaryViewModel();

            vm.Load = App.Model.UserSummaries.Load
                .Do(unit => vm.UserSource = App.Model.UserSummaries.Entities
                    .Where(us => us.IsActive))
                .ToUnit()
                .Once();

            vm.LoadEntrySummaries = (start, end) => App.Model
                .GetUtilizationSummaries(start, end)
                .Do(entries => vm.EntrySource = entries.Enumerate())
                .ToUnit();

            return vm;
        }

        private static IEntriesFlaggedForAdminViewModelService CreateEntriesFlaggedForAdminViewModel()
        {
            IEntriesFlaggedForAdminViewModelService vm = new EntriesFlaggedForAdminViewModel();

            vm.Load = App.Model
                .GetFlaggedEntries()
                .Do(entries => vm.EntrySource = entries.Enumerate())
                .ToUnit();

            return vm;
        }

        private static IEntriesPriorToProjectBillingDateViewModelService CreateEntriesPriorToProjectBillingDateViewModel()
        {
            IEntriesPriorToProjectBillingDateViewModelService vm = new EntriesPriorToProjectBillingDateViewModel();

            var load = from projects in App.Model.ProjectSummaries.Load
                           .Do(unit => vm.ProjectSource = App.Model.ProjectSummaries.Entities)
                       from entries in App.Model
                           .GetEntriesPriorToLastBillingDate()
                           .Do(entries => vm.EntrySource = entries)
                       select entries;

            vm.Load = load.Once();

            return vm;
        }
        #endregion Parameter-less

        #region Parametered

        private static IUserEditorViewModelService CreateUserEditorViewModel(Action completed)
        {
            IUserEditorViewModelService vm = new UserEditorViewModel();

            vm.CreateUserProjectEditor = callback => App.Controller.CreateUserProjectEditor(vm.User, callback);

            vm.Save = App.Model.Users.Save
                .ToUnit()
                .Do(unit => completed.TryInvoke());

            vm.Cancel = () =>
            {
                App.Model.Users.Reject.TryInvoke();
                completed.TryInvoke();
            };

            return vm;
        }

        private static IProjectEditorViewModelService CreateProjectEditorViewModel(Action completed)
        {
            IProjectEditorViewModelService vm = new ProjectEditorViewModel();

            vm.Save = App.Model.Projects.Save
                .ToUnit()
                .Do(unit => completed.TryInvoke());

            vm.Apply = App.Model.Projects.Save.ToUnit();
            vm.Cancel = () =>
                {
                    App.Model.Projects.Reject.TryInvoke();
                    completed.TryInvoke();
                };
            vm.CreateEditMembersService = App.Controller.CreateProjectMembershipEditorView;
            vm.CreateEditCategoriesService = App.Controller.CreateProjectCategoryEditorView;

            return vm;
        }

        private static IUserProjectEditorViewModelService CreateUserProjectEditorViewModel(User user, Action completed)
        {
            IUserProjectEditorViewModelService vm = new UserProjectEditorViewModel();

            ObservableCollection<ProjectMember> members = new ObservableCollection<ProjectMember>();
            vm.ProjectMembers = members;

            Subject<Unit> membersUpdated = new Subject<Unit>();
            vm.SourceUpdated = membersUpdated.AsObservable();

            var load = new List<IObservable<Unit>>();

            load.Add(App.Model
                .GetProjectMembersForUser(user.UserID)
                .Do(memberships => members.Populate(memberships))
                .Do(memberships => membersUpdated.OnNext(new Unit()))
                .Once());

            load.Add(App.Model.ProjectSummaries.Load
                .Do(unit => vm.ProjectSource = App.Model.ProjectSummaries.Entities));

            vm.Load = load
                .ForkJoin()
                .Once();

            vm.AddToUserService = summary =>
            {
                if (summary != null)
                {
                    ProjectMember pm = new ProjectMember
                    {
                        ID = Guid.NewGuid(),
                        ProjectID = summary.ProjectID,
                        UserID = user.UserID,
                        StartDate = DateTime.Now,
                        HourlyRate = Decimal.Parse(DefaultValueResources.ProjectMemberHourlyRate)
                    };

                    App.Model.ProjectMembers.Add(pm);
                    members.Add(pm);
                    membersUpdated.OnNext(new Unit());
                }
            };

            vm.RemoveFromUserService = pm =>
            {
                if (pm != null)
                {
                    App.Model.ProjectMembers.Remove(pm);
                        //.Subscribe();
                    members.Remove(pm);
                    membersUpdated.OnNext(new Unit());
                }
            };

            vm.CreateProjectMemberEditor = (project, callback) => App.Controller.CreateProjectMemberEditorView(
                vm.ProjectMembers.SingleOrDefault(pm => pm.ProjectID == project.ProjectID),
                callback);
            vm.Completed = () => App.Model.Projects.Save.Subscribe(operation => completed());

            return vm;
        }

        private static IProjectCategoryEditorViewModelService CreateProjectCategoryEditorViewModel(Project project, Action completed)
        {
            IProjectCategoryEditorViewModelService vm = new ProjectCategoryEditorViewModel();

            vm.Load = App.Model.CategorySummaries.Load
                .Do(unit => vm.CategorySource = App.Model.CategorySummaries.Entities.Where(summary => summary.IsActive));

            vm.Categories = project.ProjectCategories;

            vm.AddToProject = obj =>
            {
                if (obj != null && obj is CategorySummary)
                {
                    CategorySummary summary = (CategorySummary)obj;

                    ProjectCategory pc = new ProjectCategory
                    {
                        ProjectID = project.ProjectID,
                        CategoryID = summary.CategoryID
                    };

                    App.Model.ProjectCategories.Add(pc);
                    project.ProjectCategories.Add(pc);
                }
            };
            vm.RemoveFromProject = obj =>
            {
                if (obj != null && obj is ProjectCategory)
                {
                    ProjectCategory pc = (ProjectCategory)obj;
                    App.Model.ProjectCategories
                        .Remove(pc);
                }
            };
            vm.Completed = completed;

            return vm;
        }

        private static IGetUserPasswordViewModelService CreateGetUserPasswordViewModel(Action<IObservable<string>> registerObservable)
        {
            Subject<string> subject = new Subject<string>();
            registerObservable.TryInvoke(subject.AsObservable());

            IGetUserPasswordViewModelService vm = new GetUserPasswordViewModel();

            vm.Cancel = () => Observable.Return(string.Empty).Subscribe(subject);
            vm.OKSource = password => Observable.Return(password).Subscribe(subject);

            return vm;
        }

        private static IProjectMembershipEditorViewModelService CreateProjectMembershipEditorViewModel(Project project, Action completed)
        {
            IProjectMembershipEditorViewModelService vm = new ProjectMembershipEditorViewModel();

            vm.Load = App.Model.UserSummaries.Load
                .Do(unit => vm.MemberSource = App.Model.UserSummaries.Entities.Where(summary => summary.IsActive));

            vm.Members = project.ProjectMembers;

            vm.AddToProject = obj =>
            {
                if (obj != null && obj is UserSummary)
                {
                    UserSummary summary = (UserSummary)obj;

                    ProjectMember pm = new ProjectMember
                    {
                        ID = Guid.NewGuid(),
                        ProjectID = project.ProjectID,
                        UserID = summary.UserID,
                        StartDate = DateTime.Now,
                        EndDate = project.EstCompletionDate,
                        HourlyRate = Decimal.Parse(DefaultValueResources.ProjectMemberHourlyRate)
                    };

                    App.Model.ProjectMembers.Add(pm);
                    project.ProjectMembers.Add(pm);
                }
            };
            vm.RemoveFromProject = obj =>
            {
                if (obj != null && obj is ProjectMember)
                {
                    ProjectMember member = (ProjectMember)obj;
                    App.Model.ProjectMembers
                        .Remove(member);
                }
            };

            vm.CreateProjectMemberEditor = App.Controller.CreateProjectMemberEditorView;
            vm.Completed = completed;

            return vm;
        }

        private static IProjectMemberEditorViewModelService CreateProjectMemberEditorViewModel(ProjectMember projectMember, Action completed)
        {
            IProjectMemberEditorViewModelService vm = new ProjectMemberEditorViewModel();

            vm.Member = projectMember;
            vm.Completed = completed;

            return vm;
        }

        private static IEntryProjectCategoryAssociationEditorViewModelService CreateEntryProjectCategoryAssociationEditorViewModel(EntryLog entry, Action completed)
        {
            IEntryProjectCategoryAssociationEditorViewModelService vm = new EntryProjectCategoryAssociationEditorViewModel();

            var load = new List<IObservable<Unit>>();
            load.Add(App.Model.ProjectSummaries.Load);
            load.Add(App.Model.CategorySummaries.Load);

            var loadQuery = from pcs in App.Model.ProjectCategorySummaries.Load
                              .Do(unit => vm.ProjectCategories = App.Model.ProjectCategorySummaries.Entities)
                            from loadData in load
                              .ForkJoin()
                              .Do(unit => vm.ProjectSource = App.Model.ProjectSummaries.Entities)
                              .Do(unit => vm.CategorySource = App.Model.CategorySummaries.Entities)
                            select loadData;

            vm.Load = loadQuery
                .ToUnit()
                .Once();

            vm.Entry = entry;
            vm.Completed = completed;

            return vm;
        }

        #endregion Parametered

        #region Utilities
        private static string GetPastDueMessage(IEnumerable<EntryLog> entries)
        {
            if (entries == null)
                return null;

            var userIDs = entries.Select(e => e.UserID).Distinct();

            string message = "Entries that are past the project due date:";
            string userPart = "{0}:";
            string projectPart = "  {0}:";
            string entryPart = "    Date:{0}, Hours:{1}";

            message += Environment.NewLine;
            foreach (int userID in userIDs)
            {
                string user = string.Format(userPart, App.Model.UserSummaries.Entities.SingleOrDefault(u => u.UserID == userID).FullName);
                message = string.Format("{0}{1}{2}", message, Environment.NewLine, user);

                var userEntries = entries.Where(e => e.UserID == userID);
                var projectIDs = userEntries.Select(e => e.ProjectID).Distinct();

                foreach (int projectID in projectIDs)
                {
                    string project = string.Format(projectPart, App.Model.ProjectSummaries.Entities.SingleOrDefault(p => p.ProjectID == projectID).Name);
                    message = string.Format("{0}{1}{2}", message, Environment.NewLine, project);

                    var userProjectEntries = from e in userEntries
                                             where e.ProjectID == projectID
                                             orderby e.EntryDate
                                             select e;

                    foreach (var entry in userProjectEntries)
                    {
                        var entryMessage = string.Format(entryPart, entry.EntryDate.ToShortDateString(), entry.Duration);
                        message = string.Format("{0}{1}{2}", message, Environment.NewLine, entryMessage);
                    }
                    message += Environment.NewLine;
                }
                message += Environment.NewLine;
            }

            return message;
        }

        private static string GetInvalidProjectsMessage(IEnumerable<ProjectSummary> projects)
        {
            if (projects == null)
                return null;

            var orderedProjects = projects.OrderBy(p => p.Name);

            string message = "Projects that have invalid managers:";
            string projectPart = "{0}";

            message = message + Environment.NewLine;

            foreach (var project in orderedProjects)
            {
                string projectMessage = string.Format(
                    projectPart,
                    project.Name);
                message = string.Format("{0}{1}{2}", message, Environment.NewLine, projectMessage);
            }

            return message;
        }
        #endregion Utilities
    }
}
